VEX regalloc: track occupied real registers in a bitmask - #35
Conversation
The post-instruction house-keeping in doRegisterAllocation_v3 walked all allocatable real registers after every host instruction, even though only a handful are ever Bound or Reserved. Maintain a ULong bitmask of the rregs whose disp != Free (the universe is capped at 64 registers) and iterate only over its set bits. FREE_RREG becomes a free_rreg() helper which clears the bit; the three sites that bind or reserve an rreg set it. The invariant is verified in the allocator's existing periodic sanity checks, so a desynchronisation would be caught by the regression tests. No allocation decision changes.
Merging this PR will not alter performance
Comparing Footnotes
|
Greptile SummaryThe PR optimizes VEX register-allocation housekeeping by tracking occupied real registers in a 64-bit mask rather than scanning the full register universe after each instruction.
Confidence Score: 5/5The PR appears safe to merge, with no actionable correctness or security defects identified. Every transition between free and occupied register states updates the new mask, relocation cannot clear its destination bit because source and destination indices are distinct, and snapshot iteration visits each register occupied at the start of housekeeping exactly once.
|
| Filename | Overview |
|---|---|
| VEX/priv/host_generic_reg_alloc3.c | Replaces full real-register housekeeping scans with synchronized occupancy-mask iteration; all register disposition transitions preserve the new invariant. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Register becomes Bound or Reserved] --> B[Set occupancy bit]
B --> C[Execute host-instruction allocation]
C --> D[Snapshot occupied-register mask]
D --> E{Any set bits remain?}
E -->|Yes| F[Select lowest occupied register]
F --> G{Live range ended?}
G -->|Yes| H[free_rreg clears state and bit]
G -->|No| I[Keep register occupied]
H --> E
I --> E
E -->|No| J[Continue to next instruction]
Reviews (1): Last reviewed commit: "VEX regalloc: track occupied real regist..." | Re-trigger Greptile
Problem
Profiling the benchmark suite shows that only ~2% of a Valgrind/Callgrind run is spent in JIT-generated code — the rest is Valgrind itself, and the VEX translation pipeline is ~30% of it.
doRegisterAllocation_v3is the hottest single function of that pipeline (~4–5% of total runtime).Line-level profiling pinned ~30% of the register allocator on one loop: the post-instruction house-keeping at the end of Stage 4, which walks all allocatable real registers after every host instruction:
On amd64/arm64 that is dozens of
RRegStateentries (spread over ~14 cache lines) inspected per instruction, while in practice only a handful are everBound/Reserved— everything else isFreeand immediately skipped.Change
Maintain
ULong rregs_inuse, a bitmask where bitr_idxis set iffrreg_state[r_idx].disp != Free(the universe is already capped at 64 registers bySTATIC_ASSERT(N_RREGUNIVERSE_REGS == 64), and the allocator already usesULongregister masks).FREE_RREGbecomes a smallfree_rreg()helper that clears the bit; the three sites that bind or reserve an rreg set it.ULong__minIndex+ clear-lowest-bit), so it visits just the occupied registers.bit set <=> disp != Freeis verified inside the allocator's existing periodic sanity-check loop, so a desynchronisation would be caught by the regression tests rather than silently corrupting allocation.No allocation decisions change — this is purely a cheaper way to find the registers that need post-instruction attention.
Correctness
Both the unpatched (base) and patched (head) trees were built and their regression suites compared in the sandbox (x86_64, Debian 12):
callgrind: 23/23 pass on both.none+memcheck: the set of failing tests is identical between base and head — 30 pre-existing, environment-related failures in this container (fdleak/, stackgrowth, sigstackgrowth, rlimitnofile, map_unmap, track*, xml-track-fds, getdents_filter, bigcode — the last one aborts on anaspacemsync check caused by the sandbox, not by code generation).Register-allocation bugs produce broken generated code, so these suites are a strong check.
Performance
Measured locally with the exec harness in walltime mode (
codspeed run), 8 representative benchmarks, 15 rounds + 1s warmup each. Base and head were measured twice, alternating, to separate the signal from machine drift:Every individual benchmark moved in the same direction (faster) in both pairs, e.g.
stress-ng --cpu 1, no-inline704.6 ms → 692.7/691.4 ms,llsc_tzconvert_bench, full-with-inline412.7 ms → 408.7/409.1 ms,python3 test.py, full-with-inline2.22 s → 2.17 s. The win is modest but consistent (~3× the same-build noise floor) and architecture-independent: every translation on every target benefits. No regressions were observed.Note: the sandbox is a shared x86_64 VM, so absolute numbers are less precise than the
codspeed-macrorunners used by CI — the CodSpeed run on this PR is the authoritative measurement.